#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
#include <sys/ioctl.h>
#include <signal.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include "mongoose.h"
#include "ptylib.h"
#include "cdninfo.h"

static const char *s_url = "mqtt://47.100.111.210:1883";
static struct mg_connection *s_conn; // Client connection
static struct mg_connection *tty_conn; // Client connection
static int s_qos = 0; // MQTT QoS
static const char *s_sub_topic_fmt = "mg/%s/toboard"; // Publish topic
static const char *s_pub_topic_fmt = "mg/%s/toclient"; // Subscribe topic
static char s_sub_topic[128]; // Publish topic
static char s_pub_topic[128]; // Subscribe topic

struct hi_cdn_data *g_cdn_data;

static void fn(struct mg_connection *c, int ev, void *ev_data)
{
	if (ev == MG_EV_OPEN) {
		MG_INFO(("%lu CREATED", c->id));
		// c->is_hexdumping = 1;
	} else if (ev == MG_EV_ERROR) {
		// On error, log error message
		MG_ERROR(("%lu ERROR %s", c->id, (char *)ev_data));
		sleep(5);
	} else if (ev == MG_EV_MQTT_OPEN) {
		// MQTT connect is successful
		struct mg_str subt = mg_str(s_sub_topic);
		MG_INFO(("%lu CONNECTED to %s", c->id, s_url));
		struct mg_mqtt_opts sub_opts;
		memset(&sub_opts, 0, sizeof(sub_opts));
		sub_opts.topic = subt;
		sub_opts.qos = s_qos;
		mg_mqtt_sub(c, &sub_opts);
		MG_INFO(("%lu SUBSCRIBED to %.*s", c->id, (int)subt.len, subt.buf));
	} else if (ev == MG_EV_MQTT_MSG) {
		// When we get echo response, print it
		struct mg_mqtt_message *mm = (struct mg_mqtt_message *)ev_data;
		int mfd = *(int *)c->fn_data;
		write(mfd, mm->data.buf, mm->data.len);
		MG_INFO(("%lu RECEIVED %.*s <- %.*s", c->id, (int)mm->data.len, mm->data.buf, (int)mm->topic.len, mm->topic.buf));
		if (strcmp(mm->data.buf, "bye") == 0) {
			exit(0);
		}
	} else if (ev == MG_EV_CLOSE) {
		MG_INFO(("%lu CLOSED", c->id));
		s_conn = NULL; // Mark that we're closed
		exit(0);
	}
}

static void tty_cb(struct mg_connection *c, int ev, void *ev_data)
{
	if (ev == MG_EV_READ) {
		struct mg_iobuf *r = &c->recv;
		struct mg_mqtt_opts pub_opts;
		struct mg_str pubt = mg_str(s_pub_topic), data = mg_str_n((void *)r->buf, r->len);
		memset(&pub_opts, 0, sizeof(pub_opts));
		pub_opts.topic = pubt;
		pub_opts.message = data;
		pub_opts.qos = s_qos, pub_opts.retain = false;
		mg_mqtt_pub(s_conn, &pub_opts);
		r->len = 0;
		MG_INFO(("%lu PUBLISHED %.*s -> %.*s", s_conn->id, (int)data.len, data.buf, (int)pubt.len, pubt.buf));
	} else if (ev == MG_EV_CLOSE) {
		printf("RECV MS_EV_CLOSE\n");
		exit(0);
	}
}

int proc_client()
{
	int mfd;
	pid_t pid = pty_fork(&mfd, NULL, NULL);
	if (pid == 0) {
		//set_noecho(STDIN_FILENO);
		execlp("sh", "sh", NULL);
		exit(0);
	}

	int fds[2];
	if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, fds) < 0) {
		perror("socketpair");
		exit(0);
	}

	if (fork() == 0) {
		close(fds[1]);
		fd_set rset, org_rset;
		FD_ZERO(&org_rset);
		FD_SET(mfd, &org_rset);
		FD_SET(fds[0], &org_rset);
		int maxfd = fds[0] > mfd ? fds[0] : mfd;
		char buf[4096];
		for (;;) {
			rset = org_rset;
			if (select(maxfd + 1, &rset, NULL, NULL, NULL) <= 0) {
				continue;
			}
			if (FD_ISSET(mfd, &rset)) {
				int len = read(mfd, buf, sizeof(buf));
				if (len <= 0) {
					perror("read mfd\n");
					exit(0);
				}
				write(fds[0], buf, len);
			}
			if (FD_ISSET(fds[0], &rset)) {
				int len = read(fds[0], buf, sizeof(buf));
				if (len <= 0) {
					perror("read board");
					exit(0);
				}
				write(mfd, buf, len);
			}
		}
	}
	close(fds[0]);

	struct mg_mgr mgr;
	//mg_log_set(MG_LL_DEBUG);
	mg_mgr_init(&mgr);
	struct mg_mqtt_opts opts = { //
				     .clean = true, //
				     .qos = s_qos, //
				     .topic = mg_str(s_pub_topic), //
				     .version = 4, //
				     .user = mg_str("admin"), //
				     .pass = mg_str("JZkj@8888"),
				     .message = mg_str("bye")
	};

	s_conn = mg_mqtt_connect(&mgr, s_url, &opts, fn, &fds[1]);
	tty_conn = mg_wrapfd(&mgr, fds[1], tty_cb, NULL);

	while (1) {
		mg_mgr_poll(&mgr, 1000); // Event loop, 1s timeout
	}
	mg_mgr_free(&mgr); // Finished, cleanup
}

int current_sn(char *sn, int len)
{
	if (access("/config/work/cdn_info", F_OK) == 0) {
		struct hi_cdn_data cdn;
		int fd = open("/config/work/cdn_info", O_RDONLY);
		if (fd > 0) {
			if (read(fd, &cdn, sizeof(cdn)) == sizeof(cdn)) {
				if (strlen(cdn.cdn_sn) > 0) {
					snprintf(sn, len, "%s", cdn.cdn_sn);
					close(fd);
					return 0;
				}
			}
			close(fd);
		}
	}
	while (g_cdn_data->cdn_sn[0] == 0) {
		printf("wait cdn_sn\n");
		sleep(10);
	}
	sleep(1);
	snprintf(sn, len, "%s", g_cdn_data->cdn_sn);
	return 0;
}

int main()
{
	int lfd = open("/var/run/mqtt_board.pid", O_RDWR | O_CREAT, 0666);
	if (lockf(lfd, F_TLOCK, 0) < 0) {
		perror("master mqtt_board is runing\n");
		exit(0);
	}
	if (ftruncate(lfd, 0) < 0) {
		perror("can not ftruncate lock file");
		exit(0);
	}
	char buf[16];
	snprintf(buf, sizeof(buf), "%d", getpid());
	if (write(lfd, buf, strlen(buf)) != strlen(buf)) {
		perror("write pid file fail");
		exit(0);
	}

	int sfd = open("/tmp/cdn_info", O_RDWR | O_CREAT, 0666);
	if (sfd < 0) {
		perror("can not create cdn_info");
		exit(0);
	}
	if (ftruncate(sfd, sizeof(struct hi_cdn_data)) < 0) {
		perror("can not alloc shm");
		exit(0);
	}
	g_cdn_data = mmap(NULL, sizeof(struct hi_cdn_data), PROT_WRITE | PROT_READ, MAP_SHARED, sfd, 0);
	if (g_cdn_data == MAP_FAILED) {
		perror("mmap cnd_info");
		exit(0);
	}

	char sn[64] = { 0 };
	current_sn(sn, sizeof(sn));

	snprintf(s_pub_topic, sizeof(s_pub_topic), s_pub_topic_fmt, sn);
	snprintf(s_sub_topic, sizeof(s_sub_topic), s_sub_topic_fmt, sn);

	pid_t pid = 0;
	for (;;) {
		pid = fork();
		if (pid == 0) {
			proc_client();
			exit(0);
		}

		waitpid(pid, NULL, 0);
	}
}